// Copyright(c) 2011 Matthew McMullan, Jorel Lalicki and Mike Heise, Jorel Lalicki and Mike Heise. See copying.txt for details.

#ifndef __agml_mstatic_h__
#define __agml_mstatic_h__

// make sure that this file is never included directly
#ifndef __agml_agml_h__
#error "Include AGML from agml.h only. This file is not a valid entry point for the library."
#endif

namespace agml {
	namespace construct {
		struct _m_construct_identity {};
		_m_construct_identity identity;

		struct _m_construct_trans {};
		_m_construct_trans trans;
		struct _m_construct_transx {};
		_m_construct_transx transx;
		struct _m_construct_transy {};
		_m_construct_transy transy;
		struct _m_construct_transz {};
		_m_construct_transz transz;

		struct _m_construct_rot {};
		_m_construct_rot rot;
		struct _m_construct_rotx {};
		_m_construct_rotx rotx;
		struct _m_construct_roty {};
		_m_construct_roty roty;
		struct _m_construct_rotz {};
		_m_construct_rotz rotz;

		struct _m_construct_scale {};
		_m_construct_scale scale;
		struct _m_construct_scalex {};
		_m_construct_scalex scalex;
		struct _m_construct_scaley {};
		_m_construct_scaley scaley;
		struct _m_construct_scalez {};
		_m_construct_scalez scalez;
		struct _m_construct_scalew {};
		_m_construct_scalew scalew;
	}
	using namespace construct;
	// also used for non-member templated functions
	#define dep_elements (16/sizeof(t))
	#define dep_height ((((height*sizeof(t)+15)/16))*dep_elements)
	#define dep_data (dep_height*width)
	#define depsimd (dep_height*width)
/********************************************************************************************
*										mstatic												*
*********************************************************************************************
This is the implementation of a statically sized matrix.
Interface:
	Construction
		default constructor
			guarentees nothing. whatever was in memory
		copy constructor
			functions as expected of any copy constructor
		(construct::identity)
			Initializes it to an identity matrix. For non-square matrices it fills as much
			as it can in the top left to bottom right diagonal with 1's and sets the rest to
			zero.
	Comparison
		operator==
			Checks every element for equality with a corresponding elements in the target.
		operator!=
			Returns true where operator== would return false.
	Indexing
		operator[]
			Returns a reference to a col_t. The operator[] on that type allows this type to
			be indexed as expected with [x][y]
			Has constant and non-constant versions.
		get(u32_fast x, u32_fast y)
			Returns a REFERENCE TO the indicated element.
			Has constant and non-constant versions.
		get_row(u32_fast row)
			Returns a COPY OF the indicated row.
		get_row(u32_fast row, row_t &dest)
			Coppies the row into the destination.
		get_col(u32_fast col)
			Returns a COPY OF the indicated column.
		get_col(u32_fast col, row_t &dest)
			Coppies the column into the destination.
		get_diag()
			Returns a COPY OF the top-left to bottom-right diagonal.
		get_diag(line_t &dest)
			Coppies the top-left to bottom-right diagonal into the destination.
		set(u32_fast x, u32_fast y, const t &data)
			Sets the data at the specified location
		set_row(u32_fast row, const row_t &data)
			Sets the row to the specified data
		set_col(u32_fast col, const col_t &data)
			Sets the column to the specified data
	Addition
		operator+=
			Adds the other matrix (of the same size) to this one and returns a reference to
			this matrix.
			Also works for scalars.
		operator+
			Adds the other matrix (of the same size) to this one and returns the result.
			Also works on scalars.
		add(const mstatic &other, const mstatic &dest)
			works like operator+ except it stores the result to the specified location.
	Subtraction
		operator-=
			Subtracts the other matrix (of the same size) from this one and returns a reference
			to this matrix.
			Also works for scalars.
		operator-
			Subtracts the other matrix (of the same size) from this one and returns the result.
			Also works on scalars.
		sub(const mstatic &other, const mstatic &dest)
			works like operator- except it stores the result to the specified location.
	Multiplication
		operator*=
			Multiplies the matrix by a scalar quantity.
		operator*
			Multipies the matrix by another matrix and returns the result.
			Also works for scalars.
		mult(const mstatic &other, const mstatic &dest)
			works like operator* except it stores the result to the specified location.
	Other
		transpose()
			Transposes the matrix and returns the result
		transpose(mstatic<t,height,width> &dest)
			Transposes the matrix and stores the result to the destination
		dot()
			Returns the dot product of the matrix.
********************************************************************************************/
	template<typename t, u32 width, u32 height> struct mstatic {
		typedef vstatic<t,width> row_t;		// length of a row
		typedef vstatic<t,height> col_t;	// length of a column
		typedef vstatic<t,(width<height?width:height)> line_t;	// length of a diagonal. same as width and height in a square matrix.
		union {
			t data[dep_data];
			_v128 simd[depsimd];
		};
	// construction
		mstatic() {}
		mstatic(_m_construct_identity) {
			u32_fast i;
			for (i=0; i<dep_data; ++i) {
				data[i] = 0;
			}
			for (i=0; i<(width<height?width:height); ++i) {
				data[i+i*dep_height] = 1;
			}
		}
		mstatic(const mstatic &m) {
			for (u32 i=0; i<dep_height*width; ++i) {
				data[i] = m.data[i];
			}
		}
	// comparison
		bool operator==(const mstatic &other) const {
			for (u32 i=0; i < width*height; ++i) {
				if(data[i] != other.data[i]) {
					return 0;
				}
			}
			return 1;
		}
		bool operator!=(const mstatic &other) const {
			for (u32 i=0; i < width*height; ++i) {
				if(data[i] != other.data[i]) {
					return 1;
				}
			}
			return 0;
		}
	// indexing
		col_t &operator[](u32_fast x) {
			return *((col_t *)(&data[x*dep_height]));
		}
		const col_t &operator[](u32_fast x) const {
			return *((const col_t *)(&data[x*dep_height]));
		}
	// getters
		inline const t &get(const u32 x, const u32 y) const {
			return data[y+x*dep_height];
		}
		inline t &get(const u32 x, const u32 y) {
			return data[y+x*dep_height];
		}
		super_inline void get_diag(line_t &dest) const {
			for (u32_fast x = 0; x<(width<height?width:height); ++x) {
				dest[x] = get(x,x);
			}
		}
		line_t super_inline get_diag() const {
			line_t tmp;
			get_diag(tmp);
			return tmp;
		}
		super_inline void get_row(const u32_fast row, row_t &dest) const {
			for (u32_fast x = 0; x<width; ++x) {
				dest[x] = get(x,row);
			}
		}
		row_t super_inline get_row(const u32_fast row) const {
			row_t tmp;
			get_row(row,tmp);
			return tmp;
		}
		super_inline void get_col(const u32_fast col, row_t &dest) const {
			for (u32_fast y = 0; y<height; ++y) {
				dest[y] = get(col,y);
			}
		}
		col_t super_inline get_col(const u32_fast col) const {
			col_t tmp;
			get_col(col,tmp);
			return tmp;
		}
	// setters
		inline void set(const u32 x, const u32 y, const t &o) {
			data[y+x*dep_height] = o;
		}
		super_inline void set_row(const u32_fast row, const row_t &o) {
			for (u32_fast x = 0; x<width; ++x) {
				set(x,row,o[x]);
			}
		}
		super_inline void set_col(const u32_fast col, const col_t &o) {
			for (u32_fast y = 0; y<height; ++y) {
				set(col,y,o[y]);
			}
		}
		super_inline void set_diag(const line_t &o) {
			for (u32_fast x = 0; x<(width<height?width:height); ++x) {
				set(x,x,o[x]);
			}
		}
	// addition
		mstatic &operator+=(const mstatic &o) {
			for (u32 i = 0; i<dep_data; ++i) {
				data[i] += o.data[i];
			}
			return *this;
		}
		mstatic &operator+=(const t &o) {
			for (u32 i = 0; i<dep_data; ++i) {
				data[i] += o;
			}
			return *this;
		}
		mstatic operator+(const mstatic &o) const {
			mstatic tmp;
			for (u32 i = 0; i<dep_data; ++i) {
				tmp.data[i] = data[i] + o.data[i];
			}
			return tmp;
		}
		mstatic operator+(const t &o) const {
			mstatic tmp;
			for (u32 i = 0; i<dep_data; ++i) {
				tmp.data[i] = data[i] + o;
			}
			return tmp;
		}
		void add(const mstatic &o, mstatic &dest) {
			for (u32 i = 0; i<dep_data; ++i) {
				dest.data[i] = data[i] + o.data[i];
			}
		}
	// subtraction
		mstatic &operator-=(const mstatic &o) {
			for (u32 i = 0; i<dep_data; ++i) {
				data[i] -= o.data[i];
			}
			return *this;
		}
		mstatic &operator-=(const t &o) {
			for (u32 i = 0; i<dep_data; ++i) {
				data[i] -= o.data[i];
			}
			return *this;
		}
		mstatic operator-(const mstatic &o) const {
			mstatic tmp;
			for (u32 i = 0; i<dep_data; ++i) {
				tmp.data[i] = data[i] - o;
			}
			return tmp;
		}
		mstatic operator-(const t &o) const {
			mstatic tmp;
			for (u32 i = 0; i<dep_data; ++i) {
				tmp.data[i] = data[i] - o;
			}
			return tmp;
		}
		void sub(const mstatic &o, mstatic &dest) const {
			for (u32 i = 0; i<dep_data; ++i) {
				dest.data[i] = data[i] - o.data[i];
			}
		}
	// multiplication
		mstatic &operator*=(t o) {
			for (u32 i = 0; i<dep_data; ++i) {
				data[i] *= o;
			}
			return *this;
		}
		mstatic operator*(t o) const {
			mstatic dest;
			for (u32 i = 0; i<dep_data; ++i) {
				dest.data[i] = data[i] * o;
			}
			return dest;
		}
		template<u32 c> super_inline mstatic<t,width,c> operator*(const mstatic<t,height,c> &o) const {
			mstatic<t,width,c> dest;
			mult(o,dest);
			return dest;
		}

		//specialized 4x4 mult backup
		void mult(const mstatic<t,4,4> &o, mstatic<t,4,4> &dest) const {
			u32_fast x,y, v;
			t tmp;


			for (x=0; x<4; ++x) {

				for (y=0; y<4; ++y) {
					tmp = 0;
					for (v=0; v<4; ++v) {
						tmp += o.get(v,y)*get(x,v);
					}
					dest.set(x,y,tmp);
				}
			}



		}
		template<u32 c> void mult(const mstatic<t,height,c> &o, mstatic<t,width,c> &dest) const {
			u32_fast x,y, v;
			t tmp;
			for (x=0; x<width; ++x) {
				for (y=0; y<c; ++y) {
					tmp = 0;
					for (v=0; v<height; ++v) {
						tmp += o.get(v,y)*get(x,v);
					}
					dest.set(x,y,tmp);
				}
			}
		}
	// other math
		mstatic<t,height,width> transpose() {
			mstatic<t,height,width> o;
			u32_fast x,y;
			for (x=0; x<width; ++x) {
				for (y=0; y<height; ++y) {
					o.set(y,x,get(x,y));
				}
			}
			return o;
		}
		void transpose(mstatic<t,height,width> &dest) {
			u32_fast x,y;
			for (x=0; x<width; ++x) {
				for (y=0; y<height; ++y) {
					dest.set(y,x,get(x,y));
				}
			}
		}
		t dot(const mstatic &o) const {
			t tmp = 0;
			for (u32_fast i = 0; i<dep_data; ++i) {
				tmp += data[i] * o.data[i];
			}
			return tmp;
		}
	};
	#undef depsimd
	#undef dep_data
	#undef dep_height
	#undef dep_elements
}

#endif
